﻿using Bettzueche.RLLibrary.Functions;
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace RLProject1.Model {
    
    /// <summary>
    /// Epsilon-Greedy Strategie für eine Gridworld, implementiert IExplorationPolicy&lt;GridAction, double&gt;.
    /// <para>
    /// Mit der Wahrscheinlichkeit <i>Epsilon</i> wird die gewinnbringenste Aktion gewählt, ansonsten
    /// ein zufällige andere:<br />
    /// <code> getAction({-5, 2.2, 4.0, 0, 0, 1.0, 0, 0})</code> hat an Index 2 den höchsten Wert und liefert mit
    /// der W.keit 1-epsilon die korrespondierende Aktion GridAction.EAST
    /// </para>
    /// </summary>
    public class EpsilonGreedyForGrid : GenericExplorationPolicy<GridAction> {

        private double _epsilon;
        private Random random = new Random(DateTime.Now.Millisecond);

        /// <summary>
        /// Gibt die Wahrscheinlichkeit epsilon, nicht die bestmögliche Aktion zu wählen.<br />
        /// (Explorationsrate)<br />
        /// </summary>
        public double Epsilon {
            get { return _epsilon; }
            private set { _epsilon = Math.Max(0.0, Math.Min(1.0, value)); }
        }

        /// <summary>
        /// Erzeugt eine Strategie nach Epsilon-Greedy.
        /// </summary>
        /// <param name="epsilon">Explorationsrate von 0.0 bis 1.0. Werte außerhalb des Intervalls werden auf die Intervallgrenzen gesetzt.</param>
        public EpsilonGreedyForGrid(double epsilon) {
            Epsilon = epsilon;
        }


        #region IExplorationPolicy<GridAction,double> Members

        /// <summary>
        /// Gibt in Abhängigkeit der spezifizierten Schätzwerte ein Aktion.
        /// <para>
        /// Mit der Wahrscheinlichkeit 1-Epsilon ist dies die zum höchsten Wert gehörige Aktion:<br />
        /// <code> getAction({-5, 2.2, 4.0, 0, 0, 1.0, 0, 0})</code> hat an Index 2 den höchsten Wert und liefert mit
        /// der W.keit 1-epsilon die korrespondierende Aktion GridAction.EAST
        /// </para>
        /// </summary>
        /// <param name="qVals">Schätzwerte</param>
        /// <returns>Aktion, die aufgrund der Schätzwerte ermittelt wurde.</returns>
        public override GridAction GetAction(params double[] qVals) {
            Debug.Assert(qVals != null && qVals.Length != 0, "qVals is null or empty.");
            double rand = random.NextDouble();


            //finde max-Werte und speichere den Index(=AktionsID):
            int best = 0;
            List<int> bestList = new List<int>();
            for (int i = 0; i < qVals.Length; i++) {
                if (qVals[i] > qVals[best]) {
                    best = i;
                    bestList.Clear();
                    bestList.Add(i);
                }
                else if (qVals[i] == qVals[best])
                    bestList.Add(i);
            }

            //Im Fall (1-epsilon) oder alle Q-Vals gleich, nimm den (oder einen der) Besten.
            if (rand > Epsilon || bestList.Count == qVals.Length) {
                if (bestList.Count > 1) {
                    return (GridAction)bestList[random.Next(bestList.Count)];
                }
                return (GridAction)best;
            }

            //Im Fall (epsilon) einen zufälligen Nicht-Besten :
            int any;
            do
                any = random.Next(qVals.Length);
            while (qVals[any] == qVals[best]);
            return (GridAction)any;
        }

        #endregion
    }
}
